#include "Persistor.h"
#include "../common/utils/SurfaceUtils.h"
#include "../common/lib/SDL_rotozoom.h"

//Valid tags:
#define ASTEROIDS "asteroids"
#define TIPOS_ASTEROIDE "tiposAsteroide"
#define TIPO_ASTEROIDE "tipoAsteroide"
#define ASTEROIDES "asteroides"
#define ASTEROIDE "asteroide"
#define NAVE "nave"


#define XML_DIRECCION 	"direccion"
#define XML_TIPO		"tipo"
#define XML_VELOCIDAD	"velocidad"
#define XML_X			"x"
#define XML_Y			"y"
#define XML_NOMBRE		"nombre"
#define XML_IMAGEN		"imagen"
#define XML_ANCHO		"ancho"
#define XML_ALTO		"alto"
#define XML_ROTACION	"rotacion"

#define XML_VELROTACION	"velRotacion"
#define XML_VELMAXIMA	"velMaxima"
#define XML_PROPULSION	"propulsion"
#define XML_FRICCION	"friccion"
#define XML_DURACIONBALA "duracionBala"


#define MAX_OVERLAPING_RETRIES 40

static int width_scenario;

static int height_scenario;

Persistor::Persistor() {
}

void Persistor::getDimensions(int &width, int &height){
	//Path to scenario
	std::string scenariosPath = XML_PATH;
	char* c_scenariosPath = new char[scenariosPath.size() + 1];
	strcpy(c_scenariosPath, scenariosPath.c_str());

	//Set the doc file
	TiXmlDocument doc(c_scenariosPath);
	delete[] c_scenariosPath;

	//Is a valid file?
	if (doc.LoadFile()) {
		TiXmlElement* root = doc.RootElement();
		if (strcmp(root->Value(),ASTEROIDS) == 0){
			width = START_WIDTH_RESOLUTION;

			int tmp_width = -1;
			root->Attribute(XML_ANCHO,&tmp_width);

			if(tmp_width <= 0){
				Logger::log("Persistor","getDimensions",root,"No se encontró un ancho del escenario válido");
			}else{
				width = tmp_width;
			}

			height = START_HEIGHT_RESOLUTION;

			int tmp_height = -1;
			root->Attribute(XML_ALTO,&tmp_height);

			if(tmp_height <= 0){
				Logger::log("Persistor","getDimensions",root,"No se encontró un alto del escenario válido");
			}else{
				height = tmp_height;
			}
		}
	}
}


int Persistor::getElements(std::list<Asteroide*>& retorno, ShipStatus** shipStatus) {
	//Path to scenario
	std::string scenariosPath = XML_PATH;
	char* c_scenariosPath = new char[scenariosPath.size() + 1];
	strcpy(c_scenariosPath, scenariosPath.c_str());

	//Set the doc file
	TiXmlDocument doc(c_scenariosPath);
	delete[] c_scenariosPath;

	//Is a valid file?
	if (!doc.LoadFile()) {
		return INVALID_FILE;
	}

	TiXmlElement* root = doc.RootElement();

	//root element is a ASTEROIDS ?
	if (strcmp(root->Value(),ASTEROIDS) != 0){
		return INVALID_ROOT_VALUE;
	}

	width_scenario = START_WIDTH_RESOLUTION;

	int tmp_width = -1;
	root->Attribute(XML_ANCHO,&tmp_width);

	if(tmp_width > 0){
		width_scenario = tmp_width;
	}

	height_scenario = START_HEIGHT_RESOLUTION;

	int tmp_height = -1;
	root->Attribute(XML_ALTO,&tmp_height);

	if(tmp_height > 0){
		height_scenario = tmp_height;
	}

	///Get the list of TiposAsteroide
	std::list<DTOTipoAsteroide*> tiposAsteroide;
	int tiposReturn;
	tiposReturn = getTiposAsteroide(root,tiposAsteroide);
	if ( tiposReturn != PERSISTER_SUCCESS)
		return tiposReturn;

	//Get the asteroides
	std::list<DTOAsteroide*> asteroides;
	tiposReturn = getAsteroides(root,asteroides,tiposAsteroide);
	if ( tiposReturn != PERSISTER_SUCCESS)
		return tiposReturn;

	//Get the Ship
	std::list<DTOShipStatus*> shipsStatus;
	tiposReturn = getShip(root,shipsStatus);
	if ( tiposReturn != PERSISTER_SUCCESS)
		return tiposReturn;


	//Crear la Nave
	DTOShipStatus* dtoShipStatus;
	if(!shipsStatus.empty()){
		dtoShipStatus = shipsStatus.back();
		shipsStatus.pop_back();

		//verifico que no se superpongan con los asteroides
		resolveOverlaping(asteroides,dtoShipStatus);

		*shipStatus = new ShipStatus(dtoShipStatus);
		delete dtoShipStatus;
	}

	//Crear los Asteroides
	DTOAsteroide* asteroide;
	while(!asteroides.empty()){
		asteroide = asteroides.back();
		asteroides.pop_back();

		retorno.push_back(new Asteroide(asteroide));
		delete asteroide;
	}


	return PERSISTER_SUCCESS;
}

void Persistor::resolveOverlaping(const std::list<DTOAsteroide*>& asteroides,DTOShipStatus* dtoShipStatus){
	//Check if its overlaping with other
	bool overlaps = true;
	int retries = 0;

	std::list<DTOAsteroide*> asteroidesCopy;

	//Start the seed
	srand(time(0));

	DTOAsteroide* asteEvaluating = NULL;
	while(overlaps && retries < MAX_OVERLAPING_RETRIES ){
		overlaps = false;
		//Copy the list
		asteroidesCopy = asteroides;
		while(!asteroidesCopy.empty()){
			asteEvaluating = *(asteroidesCopy.begin());

			if (isOverlapping(asteEvaluating,dtoShipStatus)){
				dtoShipStatus->posX = getRandValueInRange(true);
				dtoShipStatus->posY = getRandValueInRange(false);


				overlaps = true;

				retries++;

				std::stringstream stream2;
				stream2 << "NUEVO VALOR TRAS colision !: ";
				stream2 << " X" << dtoShipStatus->posX;
				stream2 << " Y" << dtoShipStatus->posY;
				stream2 << " Retry :" << retries;
				Logger::log("Persistidor","isColision", &stream2);


				//Cut the while
				break;
			}

			std::stringstream stream2;
			asteroidesCopy.pop_front();
		}

	}

}

int Persistor::getRandValueInRange(bool isWidth){
	if (isWidth) {
		return (rand() % START_WIDTH_RESOLUTION);
	}

	return (rand() % START_HEIGHT_RESOLUTION);
}

bool Persistor::isOverlapping(DTOAsteroide* asteroide,DTOShipStatus* dtoShipStatus){

	if(dtoShipStatus == NULL)
		return false;

	SDL_Surface* shipSurfaceAux = SurfaceUtils::loadImage(dtoShipStatus->imagen);
	SDL_Surface* shipSurface= rotozoomSurface(shipSurfaceAux , dtoShipStatus->angle, 1.,SMOOTHING_OFF);
	SDL_FreeSurface(shipSurfaceAux);

	if(shipSurface == NULL)	return false;
	shipSurface->clip_rect.x = dtoShipStatus->posX - shipSurface->clip_rect.w/2;
	shipSurface->clip_rect.y = dtoShipStatus->posY - shipSurface->clip_rect.h/2 ;

	SDL_Surface* asteroideSurfaceAux = SurfaceUtils::loadImage(asteroide->tipo->imagen);\
	SDL_Surface* asteroideSurface= rotozoomSurface(asteroideSurfaceAux , dtoShipStatus->angle, 1.,SMOOTHING_OFF);
	asteroideSurface = SurfaceUtils::resizeSurface(asteroide->tipo->ancho,asteroide->tipo->ancho,asteroideSurface);
	SDL_FreeSurface(asteroideSurfaceAux);
	if(asteroideSurface == NULL)return false;
	asteroideSurface->clip_rect.x = asteroide->x - asteroideSurface->clip_rect.w/2;
	asteroideSurface->clip_rect.y = asteroide->y - asteroideSurface->clip_rect.h/2 ;;

	bool overlap = SurfaceUtils::overlaps(*shipSurface,*asteroideSurface);

	SDL_FreeSurface(asteroideSurface);
	SDL_FreeSurface(shipSurface);

	return overlap;

}

int Persistor::getAsteroides(TiXmlElement* root,std::list<DTOAsteroide*>& asteroides,std::list<DTOTipoAsteroide*>& tiposAsteroide){

	DTOTipoAsteroide* tipo;
	int x;
	int y;
	int direccion;
	std::string tipoString;
	int velocidad;
	bool encontrado = false;


	//Recorro el xml buscando el primer "asteroides"
	TiXmlElement* tmpXmlElement;
	TiXmlElement* xmlElement;
	for (tmpXmlElement = root->FirstChildElement(); tmpXmlElement;tmpXmlElement = tmpXmlElement->NextSiblingElement()) {
		//element is a tipoAsteroide ?
		if (strcmp(tmpXmlElement->Value(),ASTEROIDES) == 0){
			if(!encontrado){
				Logger::log("Persistor", "getAsteroides",tmpXmlElement,
					"Se encontro un elemento asteroides");
				encontrado = true;
				xmlElement = tmpXmlElement;
			}else{
				Logger::log("Persistor", "getAsteroides",tmpXmlElement,"Se encontro otro elemento asteroides");
			}
		}
	}

	// Si no se encontro una nave
	if (!encontrado){
		Logger::log("Persistor", "getAsteroides",root, "No se encontraron asteroids");
	}else{
		TiXmlElement* asteroide;
		for (asteroide = xmlElement->FirstChildElement(); asteroide;asteroide = asteroide->NextSiblingElement()) {
			tipoString = AS_DEFAULT_TIPO;
			const char* tipoS = asteroide->Attribute(XML_TIPO);
			if (tipoS != NULL){
				tipoString = tipoS;
			}else{
				Logger::log("Persistor","getAsteroides",asteroide,"No se encontró el tipo del asteroide");
			}


			x = AS_DEFAULT_X;

			int tmp_x = -1;
			asteroide->Attribute(XML_X,&tmp_x);

			if(tmp_x < 0){
				Logger::log("Persistor","getAsteroides",asteroide,"No se encontró un x del asteroide válido");
			}else{
				x = tmp_x;
			}


			y = AS_DEFAULT_Y;

			int tmp_y = -1;
			asteroide->Attribute(XML_Y,&tmp_y);

			if(tmp_y < 0){
				Logger::log("Persistor","getAsteroides",asteroide,"No se encontró un y del asteroide válido");
			}else{
				y = tmp_y;
			}


			direccion = AS_DEFAULT_DIREC;

			int tmp_dir = -1;
			asteroide->Attribute(XML_DIRECCION,&tmp_dir);

			if(tmp_dir < 0 || tmp_dir > 359){
				Logger::log("Persistor","getAsteroides",asteroide,"No se encontró una dirección del asteroide válida");
			}else{
				direccion = tmp_dir;
			}


			velocidad = AS_DEFAULT_VEL;

			int tmp_velocidad = -1;
			asteroide->Attribute(XML_VELOCIDAD,&tmp_velocidad);

			if(tmp_velocidad <  0){
				Logger::log("Persistor","getAsteroides",asteroide,"No se encontró una velocidad del asteroide válida");
			}else{
				velocidad = tmp_velocidad;
			}


			//Get the type of the asteroide from the list
			tipo = findAsteroideType(tipoString,tiposAsteroide);

			/*if (tipo->ancho <= width_scenario && tipo->alto <= height_scenario ){
				//Add the element to the list
				asteroides.push_back(new DTOAsteroide(tipo,x,y,direccion,velocidad));
			}else{
				Logger::log("Persistor","getAsteroides",asteroide,
						"El Asteroide es más grande que el scenario, se ignorará en linea:" + asteroide->Row());
			}*/
			asteroides.push_back(new DTOAsteroide(tipo,x,y,direccion,velocidad));
		}
	}

	return PERSISTER_SUCCESS;
}


int Persistor::getShip(TiXmlElement* root,std::list<DTOShipStatus*>& ship){

	std::string imagen;
	float velRotacion;
	int velMaxima;
	int propulsion;
	int friccion;
	int duracionBala;
	SDL_Surface* surfaceShip;

	bool encontrado = false;

	//Recorro el xml buscando el primer "nave"
	TiXmlElement* tmpShipElement;
	TiXmlElement* shipElement;
	for (tmpShipElement = root->FirstChildElement(); tmpShipElement;tmpShipElement = tmpShipElement->NextSiblingElement()) {
		//element is a NAVE ?
		if (strcmp(tmpShipElement->Value(),NAVE) == 0){
			if(!encontrado){
				Logger::log("Persistor", "getShip",tmpShipElement,	"Se encontro un elemento nave" );
				encontrado = true;
				shipElement = tmpShipElement;
			}else{
				Logger::log("Persistor", "getShip",tmpShipElement, "Se encontro otro elemento nave, será descartado");
			}
		}
	}

	// Si no se encontro una nave
	if (!encontrado){
		Logger::log("Persistor", "getShip",root, "Se esperaba un elemento nave, se toma la nave default");

		// Taigo imagen de la nave
		surfaceShip = SurfaceUtils::loadImage(SH_DEFAULT_IMAGE);

		// Valido si entra en el scenario
		if (surfaceShip->w <= width_scenario && surfaceShip->h <= height_scenario){
			//Add the element to the list
			ship.push_back(new DTOShipStatus());
		}else{
			Logger::log("Persistor","getShip",root,"La nave es más grande que el scenario, se ignorará.");
		}
	}else{

		imagen = SH_DEFAULT_IMAGE;
		const char*img = shipElement->Attribute(XML_IMAGEN);
		if (img != NULL){
			imagen = img;
		}else{
			Logger::log("Persistor", "getShip",shipElement, "No se encontró la imagen de la nave");
		}

		velRotacion = SH_DEFAULT_VELROT;

		double tmp_velRot = -1;
		shipElement->Attribute(XML_VELROTACION,&tmp_velRot);

		if(tmp_velRot < 0){
			Logger::log("Persistor","getShip",shipElement,"No se encontró una velocidad de rotación de la nave válida");
		}else{
			velRotacion = tmp_velRot;
		}


		velMaxima = SH_DEFAULT_VELMAX;

		int tmp_velMax = -1;
		shipElement->Attribute(XML_VELMAXIMA,&tmp_velMax);

		if(tmp_velMax < 0){
			Logger::log("Persistor","getShip",shipElement,"No se encontró una velocidad máxima de la nave válida");
		}else{
			velMaxima = tmp_velMax;
		}


		propulsion = SH_DEFAULT_PROPULSION;

		int tmp_prop = -1;
		shipElement->Attribute(XML_PROPULSION,&tmp_prop);

		if(tmp_prop < 0){
			Logger::log("Persistor","getShip",shipElement,"No se encontró una propulsión de la nave válida");
		}else{
			propulsion = tmp_prop;
		}


		friccion = SH_DEFAULT_FRICCION;

		int tmp_friccion = -1;
		shipElement->Attribute(XML_FRICCION,&tmp_friccion);

		if(tmp_friccion < 0){
			Logger::log("Persistor","getShip",shipElement,"No se encontró una fricción de la nave válida");
		}else{
			friccion = tmp_friccion;
		}


		duracionBala = SH_DEFAULT_DURBALA;

		int tmp_duracion = -1;
		shipElement->Attribute(XML_DURACIONBALA,&tmp_duracion);

		if(tmp_duracion < 0){
			Logger::log("Persistor","getShip",shipElement,"No se encontró una duración de la bala válida");
		}else{
			duracionBala = tmp_duracion;
		}

		// Taigo imagen de la nave
		surfaceShip = SurfaceUtils::loadImage(imagen);
		if (surfaceShip == NULL) {
			Logger::log("Persistor","getShip",shipElement,"La imagen de la nave es inválida, se toma imagen default");
			imagen = SH_DEFAULT_IMAGE;
			surfaceShip = SurfaceUtils::loadImage(imagen);
		}

		// Valido si entra en el scenario
		if (surfaceShip->w <= width_scenario && surfaceShip->h <= height_scenario){
			//Add the element to the list
			ship.push_back(new DTOShipStatus(imagen,velRotacion,velMaxima,propulsion,friccion,duracionBala));
		}else{
			Logger::log("Persistor","getShip",tmpShipElement,"La nave es más grande que el scenario, se ignorará.");
		}
	}
	//Se libera la memoria
	SDL_FreeSurface(surfaceShip);

	return PERSISTER_SUCCESS;
}


DTOTipoAsteroide* Persistor::findAsteroideType(std::string tipoString,std::list<DTOTipoAsteroide*>& tiposAsteroide){
	std::list<DTOTipoAsteroide*>::iterator itTipos;
	for(itTipos = tiposAsteroide.begin(); itTipos!=tiposAsteroide.end();itTipos++){
		if ((*itTipos)->nombre.compare(tipoString) == 0) {
			return *itTipos;
		}
	}

	Logger::log("Persistor", "findAsteroideType","No se encontro el tipo buscado, devuelvo tipo default");
	return new DTOTipoAsteroide();
}

bool Persistor::existsAsteroideType(std::string tipoString,std::list<DTOTipoAsteroide*> tiposAsteroide){
	std::list<DTOTipoAsteroide*>::iterator itTipos;
	for(itTipos = tiposAsteroide.begin(); itTipos!=tiposAsteroide.end();itTipos++){
		if ((*itTipos)->nombre.compare(tipoString)==0) {
			Logger::log("Persistor", "existsAsteroideType","Se encontro el tipo buscado, devuelvo true");
			return true;
		}
	}

	Logger::log("Persistor", "existsAsteroideType","No se encontro el tipo buscado, devuelvo false");
	return false;
}

int Persistor::getTiposAsteroide(TiXmlElement* root,std::list<DTOTipoAsteroide*>& tiposAsteroide){

	std::string nombre;
	std::string imagen;
	int ancho;
	int alto;
	int rotacion;
	bool encontrado = false;

	TiXmlElement* tmpTiposAsteroideElement;
	TiXmlElement* tiposAsteroideElement;
	for (tmpTiposAsteroideElement = root->FirstChildElement(); tmpTiposAsteroideElement; tmpTiposAsteroideElement = tmpTiposAsteroideElement->NextSiblingElement()) {

		if (strcmp(tmpTiposAsteroideElement->Value(),TIPOS_ASTEROIDE) == 0){
			if(!encontrado){
				Logger::log("Persistor", "getTiposAsteroide",tmpTiposAsteroideElement, "Se encontro un elemento tiposAsteroide");
				encontrado = true;
				tiposAsteroideElement = tmpTiposAsteroideElement;
			}else{
				Logger::log("Persistor", "getTiposAsteroide",tmpTiposAsteroideElement,
									"Se encontro otro elemento tiposAsteroide, sera ignorado");
			}
		}
	}

	if (!encontrado){
		Logger::log("Persistor", "getTiposAsteroide", tiposAsteroideElement,"No se encontraron elementos tiposAsteroide");
	}else{

		TiXmlElement* tipoAsteroide;
		for (tipoAsteroide = tiposAsteroideElement->FirstChildElement(); tipoAsteroide; tipoAsteroide = tipoAsteroide->NextSiblingElement()) {

			if (strcmp(tipoAsteroide->Value(),TIPO_ASTEROIDE) == 0){
				nombre = TA_DEFAULT_NAME;
				const char* nom = tipoAsteroide->Attribute(XML_NOMBRE);
				if (nom != NULL){
					nombre = nom;
				}else{
					Logger::log("Persistor","getTiposAsteroide",tipoAsteroide,"No se encontró un nombre del tipo de asteroide");
				}


				imagen = TA_DEFAULT_IMAGE;
				const char*img = tipoAsteroide->Attribute(XML_IMAGEN);
				if (img != NULL){
					imagen = img;
				}else{
					Logger::log("Persistor","getTiposAsteroide",tipoAsteroide,"No se encontró una imagen del tipo de asteroide");
				}


				ancho = TA_DEFAULT_WIDTH;

				int tmp_ancho = -1;
				tipoAsteroide->Attribute(XML_ANCHO,&tmp_ancho);

				if(tmp_ancho < 0){
					Logger::log("Persistor","getTiposAsteroide",tipoAsteroide,"No se encontró un ancho del tipo de asteroide válido");
				}else{
					ancho = tmp_ancho;
				}


				alto = TA_DEFAULT_HEIGTH;

				int tmp_alto = -1;
				tipoAsteroide->Attribute(XML_ALTO,&tmp_alto);

				if(tmp_alto < 0){
					Logger::log("Persistor","getTiposAsteroide",tipoAsteroide,"No se encontró un alto del tipo de asteroide válido");
				}else{
					alto = tmp_alto;
				}


				rotacion = TA_DEFAULT_ROTATION;

				int tmp_rotacion = -1;
				tipoAsteroide->Attribute(XML_ROTACION,&tmp_rotacion);

				if(tmp_rotacion < 0 || tmp_rotacion > 359){
					Logger::log("Persistor","getTiposAsteroide",tipoAsteroide,"No se encontró una rotación del tipo de asteroide válida");
				}else{
					rotacion = tmp_rotacion;
				}


				if(!existsAsteroideType(nombre,tiposAsteroide)){
					//Add the element to the list
					Logger::log("Persistor", "getTiposAsteroide",tipoAsteroide,"Nuevo tipo '" + nombre + "', lo agrego");
					tiposAsteroide.push_back(new DTOTipoAsteroide(nombre,imagen,ancho,alto,rotacion));
				}else{
					Logger::log("Persistor", "getTiposAsteroide","Ya existe el tipo '" + nombre + "', lo ignoro");
				}
			}else{
				Logger::log("Persistor", "getTiposAsteroide",tipoAsteroide,"Elemento diferente a tipoAsteroide, lo ignoro");
			}
		}
	}

	return PERSISTER_SUCCESS;
}
